﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;

namespace IOPerformanceVerifier
{
    public class IOPVFactory
    {
        static IOPVFactory _iopvFactoryInstance;
        Logger _logger = null;

        public IOPVFactory()
        {
            _logger = new Logger();
        }

        public Logger getLogger()
        {
            return _logger;
        }

        public static IOPVFactory getInstance()
        {
            if (_iopvFactoryInstance == null)
            {
                _iopvFactoryInstance = new IOPVFactory();
            }
            return _iopvFactoryInstance;
        }

        public List<ThreadConfigutation> loadConfigurations(float pressureMultiplier, string configurationFile)
        {
            List<ThreadConfigutation> threadConfigurationList = new List<ThreadConfigutation>();
            loadAndParseConfigurations(configurationFile, pressureMultiplier, threadConfigurationList);

            if (threadConfigurationList.Count == 0)
            {
                throw new Exception("No disk tests set");
            }

            // validate set values
            foreach (ThreadConfigutation threadConfiguration in threadConfigurationList)
            { 
                if (!(threadConfiguration.IsDiskTestNameSet && 
                    threadConfiguration.IsLogicalDriveLetterSet && 
                    threadConfiguration.IsDataFilePlacementSet && 
                    threadConfiguration.IsPressureMultiplierSet &&
                    threadConfiguration.IsNonBufferedWriteSet &&
                    threadConfiguration.IsBlockWriteSet &&
                    threadConfiguration.IsBlockSizeSet &&
                    threadConfiguration.IsReadPerUnitSet &&
                    threadConfiguration.IsWritePerUnitSet &&
                    threadConfiguration.IsReadsPerSecondSet &&
                    threadConfiguration.IsWritesPerSecondSet &&
                    threadConfiguration.IsNumberOfThreadsSet &&
                    threadConfiguration.IsUnitNameSet &&
                    threadConfiguration.IsUnitFactorSet &&
                    threadConfiguration.IsFileSizeSet &&
                    threadConfiguration.IsSequentialIOSet &&
                    threadConfiguration.IsRandomIODistributedSet
                    ))
                {
                    throw new Exception("Not all configuration values are set for " + threadConfiguration.DiskTestName);
                }
            }

            return threadConfigurationList;
        }

        private void loadAndParseConfigurations(String configurationFile,float pressureMultiplier, List<ThreadConfigutation> threadConfigurationList)
        {
            String[] configurationLines = File.ReadAllLines(configurationFile);
            try
            {
                configurationLines = File.ReadAllLines(configurationFile);
                ThreadConfigutation generalThreadConfiguration = new ThreadConfigutation();
                generalThreadConfiguration.PressureMultiplier = pressureMultiplier;
                ThreadConfigutation individualThreadConfiguration = null;
                bool inGeneralConfigurationArea= false;
                int lineNumber = 0;

                // Read all general configurations
                foreach (String configurationLine in configurationLines)
                {
                    lineNumber += 1;
                    string trimmedConfigurationLine = configurationLine.Trim();
                    if (string.IsNullOrEmpty(trimmedConfigurationLine))
                    {
                        // Ignore line
                    }
                    else if (trimmedConfigurationLine.StartsWith("#"))
                    {
                       // Ignore line
                        _logger.log(LogLevel.Debug, "Ignores comment line (line " + lineNumber + "): " + trimmedConfigurationLine);
                    }
                    else if (trimmedConfigurationLine.StartsWith("["))
                    {
                        // Start of new configuration
                        if(trimmedConfigurationLine.Equals("[General]"))
                        {
                            _logger.log(LogLevel.Debug, "Loading general configuration (line " + lineNumber + ")");
                            inGeneralConfigurationArea = true;
                        }
                        else
                        {
                            inGeneralConfigurationArea = false;
                            if (individualThreadConfiguration != null)
                            {
                                threadConfigurationList.Add(individualThreadConfiguration);
                            }
                            individualThreadConfiguration = generalThreadConfiguration.cloneConfiguration();
                            string diskTestName = trimmedConfigurationLine.Replace("[", "").Replace("]", "");
                            individualThreadConfiguration.DiskTestName = diskTestName;
                            _logger.log(LogLevel.Debug, "Loading individual configuration (line " + lineNumber + ") for: " + diskTestName);
                        }
                    }
                    else
                    {
                        if (trimmedConfigurationLine.StartsWith("LogicalDriveLetter"))
                        {
                            String logicalDriveLetter = parseLogicalDriveLetter(lineNumber,trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.LogicalDriveLetter = logicalDriveLetter;
                            }
                            else
                            {
                                individualThreadConfiguration.LogicalDriveLetter = logicalDriveLetter;
                            }
                            _logger.log(LogLevel.Debug, "LogicalDriveLetter (line " + lineNumber + "): " + logicalDriveLetter);
                        }
                        else if(trimmedConfigurationLine.StartsWith("DataFilePlacement"))
                        {
                            string dataFilePlacement = parseDataFilePlacement(lineNumber,trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.DataFilePlacement = dataFilePlacement;
                            }
                            else
                            { 
                                individualThreadConfiguration.DataFilePlacement = dataFilePlacement;
                            }
                            _logger.log(LogLevel.Debug, "DataFilePlacement (line " + lineNumber + "): " + dataFilePlacement);
                        }
                        else if(trimmedConfigurationLine.StartsWith("NonBufferedWrite"))
                        {
                            bool nonBufferedWrite = parseNonBufferedWrite(lineNumber,trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.NonBufferedWrite =nonBufferedWrite;
                            }
                            else
                            { 
                                individualThreadConfiguration.NonBufferedWrite = nonBufferedWrite;
                            }
                            _logger.log(LogLevel.Debug, "NonBufferedWrite (line " + lineNumber + "): " + nonBufferedWrite);
                        }
                        else if(trimmedConfigurationLine.StartsWith("BlockWrite"))
                        {
                            bool blockWrite = parseBlockWrite(lineNumber,trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.BlockWrite = blockWrite;
                            }
                            else
                            { 
                                individualThreadConfiguration.BlockWrite = blockWrite;
                            }
                            _logger.log(LogLevel.Debug, "BlockWrite (line " + lineNumber + "): " + blockWrite);
                        }
                        else if(trimmedConfigurationLine.StartsWith("BlockSize"))
                        {
                            int blockSize = parseBlockSize(lineNumber,trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.BlockSize = blockSize;
                            }
                            else
                            { 
                                individualThreadConfiguration.BlockSize = blockSize;
                            }
                            _logger.log(LogLevel.Debug, "BlockSize (line " + lineNumber + "): " + blockSize);
                        }
                        else if (trimmedConfigurationLine.StartsWith("ReadPerUnit"))
                        {
                            float readPerUnit = parseReadPerUnit(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.ReadPerUnit = readPerUnit;
                            }
                            else
                            {
                                individualThreadConfiguration.ReadPerUnit = readPerUnit;
                            }
                            _logger.log(LogLevel.Debug, "ReadPerUnit (line " + lineNumber + "): " + readPerUnit);
                        }
                        else if (trimmedConfigurationLine.StartsWith("WritePerUnit"))
                        {
                            float writePerUnit = parseWritePerUnit(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.WritePerUnit = writePerUnit;
                            }
                            else
                            {
                                individualThreadConfiguration.WritePerUnit = writePerUnit;
                            }
                            _logger.log(LogLevel.Debug, "WritePerUnit (line " + lineNumber + "): " + writePerUnit);
                        }
                        else if (trimmedConfigurationLine.StartsWith("ReadsPerSecond"))
                        {
                            float readsPerSecond = parseReadsPerSecond(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.ReadsPerSecond = readsPerSecond;
                            }
                            else
                            {
                                individualThreadConfiguration.ReadsPerSecond = readsPerSecond;
                            }
                            _logger.log(LogLevel.Debug, "ReadsPerSecond (line " + lineNumber + "): " + readsPerSecond);
                        }
                        else if (trimmedConfigurationLine.StartsWith("WritesPerSecond"))
                        {
                            float writesPerSecond = parseWritesPerSecond(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.WritesPerSecond = writesPerSecond;
                            }
                            else
                            {
                                individualThreadConfiguration.WritesPerSecond = writesPerSecond;
                            }
                            _logger.log(LogLevel.Debug, "WritesPerSecond (line " + lineNumber + "): " + writesPerSecond);
                        }
                        else if (trimmedConfigurationLine.StartsWith("NumberOfThreads"))
                        {
                            int numberOfThreads = parseNumberOfThreads(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.NumberOfThreads = numberOfThreads;
                            }
                            else
                            {
                                individualThreadConfiguration.NumberOfThreads = numberOfThreads;
                            }
                            _logger.log(LogLevel.Debug, "NumberOfThreads (line " + lineNumber + "): " + numberOfThreads);
                        }
                        else if (trimmedConfigurationLine.StartsWith("UnitName"))
                        {
                            string unitName = parseUnitName(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.UnitName = unitName;
                            }
                            else
                            {
                                individualThreadConfiguration.UnitName = unitName;
                            }
                            _logger.log(LogLevel.Debug, "UnitName (line " + lineNumber + "): " + unitName);
                        }
                        else if (trimmedConfigurationLine.StartsWith("UnitFactor"))
                        {
                            float unitFactor = parseUnitFactor(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.UnitFactor = unitFactor;
                            }
                            else
                            {
                                individualThreadConfiguration.UnitFactor = unitFactor;
                            }
                            _logger.log(LogLevel.Debug, "UnitFactor (line " + lineNumber + "): " + unitFactor);
                        }
                        else if (trimmedConfigurationLine.StartsWith("FileSize"))
                        {
                            int fileSize = parseFileSize(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.FileSize = fileSize;
                            }
                            else
                            {
                                individualThreadConfiguration.FileSize = fileSize;
                            }
                            _logger.log(LogLevel.Debug, "FileSize (line " + lineNumber + "): " + fileSize);
                        }
                        else if (trimmedConfigurationLine.StartsWith("SequentialIO"))
                        {
                            bool sequentialIO = parseSequentialIO(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.SequentialIO = sequentialIO;
                            }
                            else
                            {
                                individualThreadConfiguration.SequentialIO = sequentialIO;
                            }
                            _logger.log(LogLevel.Debug, "SequentialIO (line " + lineNumber + "): " + sequentialIO);
                        }
                        else if (trimmedConfigurationLine.StartsWith("RandomIODistributed"))
                        {
                            bool randomIODistributed = parseRandomIODistributed(lineNumber, trimmedConfigurationLine);
                            if (inGeneralConfigurationArea)
                            {
                                generalThreadConfiguration.RandomIODistributed = randomIODistributed;
                            }
                            else
                            {
                                individualThreadConfiguration.RandomIODistributed = randomIODistributed;
                            }
                            _logger.log(LogLevel.Debug, "RandomIODistributed (line " + lineNumber + "): " + randomIODistributed);
                        }
                        else
                        {
                            _logger.log(LogLevel.Error, "Unknown configuration (line " + lineNumber + "): " + trimmedConfigurationLine);
                        }
                    }
                }
                if (individualThreadConfiguration != null)
                {
                    ThreadConfigutation readThreadConfiguration = null;
                    ThreadConfigutation writeThreadConfiguration = null;
                    for (int threadNumber = 0; threadNumber < individualThreadConfiguration.NumberOfThreads; threadNumber++)
                    {
                        if (individualThreadConfiguration.ReadPerUnit > 0 && individualThreadConfiguration.WritePerUnit > 0)
                        {
                            writeThreadConfiguration = individualThreadConfiguration.cloneConfiguration();
                            writeThreadConfiguration.ThreadType = IOThreadType.Write;
                            writeThreadConfiguration.ThreadNumber = threadNumber;
                            threadConfigurationList.Add(writeThreadConfiguration);
                            readThreadConfiguration = individualThreadConfiguration.cloneConfiguration();
                            readThreadConfiguration.ThreadType = IOThreadType.Read;
                            readThreadConfiguration.ThreadNumber = threadNumber;
                            threadConfigurationList.Add(readThreadConfiguration);
                        }
                        else if (individualThreadConfiguration.WritePerUnit > 0)
                        {
                            writeThreadConfiguration = individualThreadConfiguration.cloneConfiguration();
                            writeThreadConfiguration.ThreadType = IOThreadType.Write;
                            writeThreadConfiguration.ThreadNumber = threadNumber;
                            threadConfigurationList.Add(writeThreadConfiguration);
                        }
                        else
                        {
                            readThreadConfiguration = individualThreadConfiguration.cloneConfiguration();
                            readThreadConfiguration.ThreadType = IOThreadType.Read;
                            readThreadConfiguration.ThreadNumber = threadNumber;
                            threadConfigurationList.Add(readThreadConfiguration);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                _logger.log(LogLevel.Error, e);
            }
        }

        private float parseWritesPerSecond(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("writespersecond", "").Trim().Replace(":", "").Replace(",", ".").Trim();
            float floatResult;

            if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out floatResult))
            {
                return floatResult;
            }
            else
            {
                throw new Exception("WritesPerSecond could not be parsed in line " + lineNumber + " with text:" + trimmedConfigurationLine);
            }
        }

        private float parseReadsPerSecond(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("readspersecond", "").Trim().Replace(":", "").Replace(",", ".").Trim();
            float floatResult;

            if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out floatResult))
            {
                return floatResult;
            }
            else
            {
                throw new Exception("ReadsPerSecond could not be parsed in line " + lineNumber + " with text:" + trimmedConfigurationLine);
            }
        }

        private bool parseRandomIODistributed(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("randomiodistributed", "").Trim().Replace(":", "").Trim();

            if (value.Equals("true") || value.Equals("yes") || value.Equals("1"))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private bool parseSequentialIO(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("sequentialIO", "").Trim().Replace(":", "").Trim();

            if (value.Equals("true") || value.Equals("yes") || value.Equals("1"))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private int parseFileSize(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("filesize", "").Trim().Replace(":", "").Trim();
            int intResult;

            if (int.TryParse(value, out intResult))
            {
                return intResult;
            }
            else
            {
                throw new Exception("FileSize could not be parsed in line " + lineNumber + " with text:" + trimmedConfigurationLine);
            }
        }

        private float parseUnitFactor(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("unitfactor", "").Trim().Replace(":", "").Replace(",", ".").Trim();
            float floatResult;

            if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out floatResult))
            {
                return floatResult;
            }
            else
            {
                throw new Exception("UnitFactor could not be parsed in line " + lineNumber + " with text:" + trimmedConfigurationLine);
            }
        }

        private string parseUnitName(int lineNumber, string trimmedConfigurationLine)
        {
            return trimmedConfigurationLine.ToLower().Replace("unitname", "").Trim().Replace(":", "").Trim();
        }

        private int parseNumberOfThreads(int lineNumber,string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("numberofthreads", "").Trim().Replace(":", "").Trim();
            int intResult;

            if (int.TryParse(value, out intResult))
            {
                return intResult;
            }
            else
            {
                throw new Exception("NumberOfThreads could not be parsed in line " + lineNumber + " with text:" + trimmedConfigurationLine);
            }
        }

        private float parseWritePerUnit(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("writeperunit", "").Trim().Replace(":", "").Replace(",", ".").Trim();
            float floatResult;

            if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out floatResult))
            {
                return floatResult;
            }
            else
            {
                throw new Exception("WritePerUnit could not be parsed in line " + lineNumber + " with text:" + trimmedConfigurationLine);
            }
        }

        private float parseReadPerUnit(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("readperunit", "").Trim().Replace(":", "").Replace(",", ".").Trim();
            float floatResult;

            if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out floatResult))
            {
                return floatResult;
            }
            else
            {
                throw new Exception("ReadPerUnit could not be parsed in line " + lineNumber + " with text:" + trimmedConfigurationLine);
            }
        }

        private int parseBlockSize(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("blocksize", "").Trim().Replace(":", "").Trim();
            int intResult;

            if (int.TryParse(value, out intResult))
            {
                return intResult;
            }
            else
            {
                throw new Exception("BlockSize could not be parsed in line " + lineNumber + " with text:" + trimmedConfigurationLine);
            }
        }

        private bool parseBlockWrite(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("blockwrite", "").Trim().Replace(":", "").Trim();

            if (value.Equals("true") || value.Equals("yes") || value.Equals("1"))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private bool parseNonBufferedWrite(int lineNumber, string trimmedConfigurationLine)
        {
            string value = trimmedConfigurationLine.ToLower().Replace("nonbufferedwrite", "").Trim().Replace(":", "").Trim();

            if (value.Equals("true") || value.Equals("yes") || value.Equals("1"))
            {
                return true;
            }
            else
            { 
                return false; 
            }
        }

        private string parseDataFilePlacement(int lineNumber, string trimmedConfigurationLine)
        {
            return trimmedConfigurationLine.ToLower().Replace("datafileplacement", "").Trim().Replace(":", "").Trim();
        }

        private string parseLogicalDriveLetter(int lineNumber, string trimmedConfigurationLine)
        {
            return trimmedConfigurationLine.ToLower().Replace("logicaldriveletter", "").Trim().Replace(":", "").Trim();
        }

    }
}
